package com.dy.high.readt;

import org.junit.Test;

import java.io.*;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * 一家企业的机试题
 *
 *
 * 数据示例
 *
 * 设备号:设备名称
 * 9:.visu_operation_mode_nr,10:.wind_speed,11:.wind_vane_wind_direction,12:.yaw_position,13:.ambient_temperature,14:.grid_active_power,15:.generator_speed_momentary,16:.pitch_position_blade_1
 *
 *  时间,设备号,数据
 * 1493284687508,12,-410.57852
 * 时间（同一时间，省略其他设备时间，节约设备存储）,设备号,数据
 * ,13,17.5
 * ,14,112.6693
 * ,15,11.57795
 * ,16,-0.79
 * ,9,5
 * ,10,5.87138
 * ,11,159.61699
 * 1493284687518,10,5.87138
 *
 * 需求：
 *  1、读取txt数据；然后补全没有时间的数据，输出到txt文件
 *  2、机器每20ms发送一次数据，这里需要不全数据，由于网络或者其他原因导致间隔1个20ms或者n个20ms没有发送，就需要补全；
 *   补全规则中 如果 n点没有数据的话，应该复制一份n-1的数据
 *
 *  data_input目录下
 *  value_map 为改程序写出的样本,value_map4 为面试方提供的样本数据
 *  （最终的结果发现面试方数据 和他们的实际要求会有几百条数据是错误的，图1、图2）
 *
 *
 * @author huangdeyao
 * @date 2019/8/23 21:31
 */
public class ReadTxtTest7 {
    /**
     * 头数据 这里使用LinkedHashMap 保证输入跟输出的顺序保持一致
     */
    private static Map<String, String> titleMap = new LinkedHashMap<>();
    /**
     * 定义一个与头信息一样的map 方便后面数据跟头数据的顺序保持一致
     */
    private static Map<String, String> contextMap = new LinkedHashMap<>();
    /**
     * 存一份缓存数据map
     */
    private Map<String, String> tempMap = new LinkedHashMap<>();
    /**
     * 基准时间
     */
    private long baseTime = 0;
    /**
     * 读取txt 数据
     */
    private BufferedReader br;

    {
        try {
            br = new BufferedReader(new InputStreamReader(new FileInputStream(new File("C://data_input//in_data.txt")), "UTF-8"));
        } catch (UnsupportedEncodingException | FileNotFoundException e) {
            e.printStackTrace();
        }
    }

    /**
     * 写入数据  Spring环境上可以考虑使用log日志方式
     * <p>
     * 写入数据地址
     */
    private BufferedWriter bw;

    {
        try {
            bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(new File("C://data_input//value_map.txt")), "UTF-8"));
        } catch (UnsupportedEncodingException | FileNotFoundException e) {
            e.printStackTrace();
        }
    }

    /**
     * 测试程序入口
     */
    @Test
    public void dealData() throws IOException {
        // 开时时间
        long bTime1 = System.currentTimeMillis();
        /**
         * 主程序
         */
        readTxt();
        //关闭总计时
        long eTime1 = System.currentTimeMillis();
        //输出
        System.out.println("共耗时：" + (eTime1 - bTime1));
    }

    /**
     * 读数据
     */
    private void readTxt() throws IOException {
        try {
            String lineTxt = null;
            while ((lineTxt = br.readLine()) != null) {
                // 数据切割
                String[] strings = lineTxt.split(",");
                // 第一行头数据截取后长度大于3，其他数据集合长度截取后长度为3，此处使用该条件判断当前行数是否为头
                if (strings.length > 3) {
                    // 装载头信息
                    titleHandle(strings);
                } else {
                    // 处理数据
                    handleContext(strings);
                }
            }
            /**
             * 代码写入执行为n - 1;所以会遗留一条数据，这里写入最后一条数据
             * 最后一条数据也有可能缺少时间点  这里继续传入递归处理
             */
            handleSwp(contextMap);
            String previousContext = tempMap.get(String.valueOf(baseTime));
            writerHandle(previousContext);
            System.out.println("tempMap = " + tempMap);
            /**
             * 关闭
             */
            br.close();
            bw.close();
        } catch (Exception e) {
            System.err.println("read errors :" + e);
        } finally {
            bw.close();
        }
    }

    /**
     * 组装完整数据 该方法执行完成后 contextMap中存储的是当前时间戳完整数据
     *
     * @param strings 切割数据
     */
    private void handleContext(String[] strings) throws IOException {
        if (null != strings[0] && !"".equals(strings[0])) {
            if (baseTime == 0) {
                baseTime = Long.valueOf(strings[0]);
            } else {
                // 执行这里的时候 说明上一条数据已经组装完成
                handleSwp(contextMap);
            }
            contextMap.put("timestamp", strings[0]);
        }
        // 将值写入map, map中的数据如果对应的编码有值，会修改，没有值继续保持之前的数据，
        // 当前数据如果没有时间戳，此条数据会写入contextMap，不修改时间戳
        handleMap(strings);
    }

    /**
     * 使用递归处理数据逻辑
     *
     * @param contextMap
     */
    private void handleSwp(Map<String, String> contextMap) throws IOException {
        /**
         * 当前读取行号时间
         */
        long nowTimes = Long.valueOf(contextMap.get("timestamp"));
        if (nowTimes == 1493284687578L) {
            System.out.println("contextMap = " + nowTimes);
        }
        /**
         * 每次执行 都检查缓存里面是否有上一条数据，有就写入txt,清空
         */
        String tempContext = writeMapTxt(baseTime - 20);
        // 如果当前时间等于基准时间 写入缓存tempMap 中
        if (baseTime == nowTimes) {
            contextMap.put("timestamp", String.valueOf(baseTime));
            tempMap.put(String.valueOf(baseTime), handleString(contextMap));
            // 执行写入后 说明当前信号时间结束  开始下一个信号时间 此处 + 20ms
            baseTime = baseTime + 20;
        } else if (baseTime > nowTimes) {
            contextMap.put("timestamp", String.valueOf(baseTime));
            // 基准时间大于当前信号时间，说明当前信号提前，更正时间，写入缓存tempMap 中； 这里不能 + 20ms 因为后面不知道是否还有数据在当前信号时间前
            tempMap.put(String.valueOf(baseTime), handleString(contextMap));
        } else {
            // 说明缺失信号数据，这里需要cp上一次的数据
            if (tempContext != null) {
                // cp 上一条数据
                String nowData = tempContext.replaceFirst(String.valueOf(baseTime - 20), String.valueOf(baseTime));
                // 写入当前标准时间
                tempMap.put(String.valueOf(baseTime), nowData);
            } else {
                // 等于null 的时候说明 n-1 次执行的时候，baseTime > nowTimes 数据保存在tempMap中,而对应的时间节点没有步进一位，下一个循环的时间还是n-1，
                // tempMap中对应的时间节点也是n-1，取tempContext值的时候需要向上步进一位 传值为n-1-1,所有取不到。
                //  n-1值在下一个步进步入上一个节点，也就是缺失的信号点
            }
            // 这里补充完后数据数据  说明至少缺失的那一条数据信号完成 所以这里需要步进一次
            baseTime = baseTime + 20;
            handleSwp(contextMap);
        }
    }

    /**
     * 写入txt
     *
     * @param previousTime 返回当前删除的数据,防止下一条数据有缺失信号时间的情况
     * @return
     * @throws IOException
     */
    private String writeMapTxt(long previousTime) throws IOException {
        /**
         * 取出一条数据 写入
         */
        String previousContext = tempMap.get(String.valueOf(previousTime));
        if (null != previousContext && !"".equals(previousContext)) {
            // 这里可以找到值，说明时间戳已经走到了下一个 这里进行写入操作
            writerHandle(previousContext);
            tempMap.remove(String.valueOf(previousTime));
        }
        return previousContext;
    }

    /**
     * 写入txt
     */
    private void writerHandle(String string) {
        try {
            bw.write(string);
            bw.newLine();
        } catch (Exception e) {
            System.err.println("write errors :" + e);
        }
    }

    /**
     * 装载头信息
     *
     * @param strings 读取的当前行数据
     */
    private void titleHandle(String[] strings) {
        // 初始化数据存储map的时间戳
        contextMap.put("timestamp", "0");
        StringBuilder title = new StringBuilder("timestamp");
        for (String str : strings) {
            String[] headTxt = str.split(":");
            // 头信息存入map 用于后面便利数据
            titleMap.put(headTxt[1], headTxt[0]);
            // 装载一个与头信息一样的map 方便后面数据跟头数据的顺序保持一致
            contextMap.put(headTxt[1], "0");
            // 组装头信息
            title.append(";").append(headTxt[1]);
        }
        // 写入
        writerHandle(String.valueOf(title));
    }

    /**
     * 将数据中的值（contextMap）与头信息（titleMap）一一对应赋值
     * 这里的数据 n  覆盖 n-1
     *
     * @param strings 读取的当前行数据
     */
    private void handleMap(String[] strings) {
        // 查找当前数据 相对于头数据的属性
        for (String name : titleMap.keySet()) {
            if (titleMap.get(name).equals(strings[1])) {
                contextMap.put(name, strings[2]);
                // 如果当前时间节点数据组块里面有没有重复编码数据，此处找到数据后可以使用break;提高效率
                break;
            }
        }
    }

    /**
     * 将map 中的值 组装成string
     *
     * @param map
     * @return
     */
    private String handleString(Map<String, String> map) {
        // 将上一个timestamp 还原，否则当前时间为n+1行的时间，没有在赋值前做是因为如果数据为最后一行，将少一条数据
        // 组装写入txt的 string
        StringBuilder context = new StringBuilder();
        for (String name : map.keySet()) {
            // 这里为了删除最后一个；
            if ("timestamp".equals(name)) {
                context.append(map.get(name));
            } else {
                context.append(";").append(map.get(name));
            }
        }
        return String.valueOf(context);
    }

}
